React エラーハンドリング
#react #例外処理
概要
Reactアプリ開発するときのエラーハンドリングどうしよっかなぁ〜〜てなってる。
思考してどういう風にするのがいいか整理しようかなって。
戦略から固めていくかぁ?
指針(仮)(ちょとよくわからんので、一旦仮で...徐々に改善できれば良いかもです)
hr.icon
レンダリング系のエラー
1. 最上位にErrorBoundaryを置いて、全コンポーネントにてキャッチされなかったレンダリング系例外の最後の砦とする。(「何かエラーが起きました」的なページを表示)
2. 一部のコンポーネントにて個別に例外対応したい場合は、そのコンポーネントに閉じたErrorBoundaryを作成する。
3. エラーが起きたらトップページに戻らせるボタンを置くのはアリかもしれない。
参考:Reactベストプラクティスの宝庫!「bulletproof-react」が勉強になりすぎる件
非同期、イベントハンドラー系のエラー
1. 最上位にエラーキャッチのハンドラー(onerror)を登録しておき、そこにエラーが届いた場合はログをどっかのサーバーに送信する。
どこにもキャッチされなかった例外を放置しておくのは危険。
2. 個別のエラーに関しては、できる限りそのルーチン内もしくはその近くで対処するように努める。
[基本]「ErrorBoundary」を使ってエラーハンドリング
hr.icon
コンポーネント表示に関してエラーが発生した場合、バージョンによっては全コンポーネントがアンマウントされて、画面が真っ白になるらしい。
参考:Component – React
By default, if your application throws an error during rendering, React will remove its UI from the screen. To prevent this, you can wrap a part of your UI into an error boundary. An error boundary is a special component that lets you display some fallback UI instead of the part that crashed—for example, an error message.
画面真っ白にしてユーザーを路頭に迷わせないように、エラー発生を検知してエラー画面を表示するようにしたい。
そのエラーキャッチするコンポーネントのことをErrorBoundaryと呼ぶらしい。
ただ自分で実装するのは面倒くさそうなのでライブラリを使うといい。
react-error-boundaryっていうやつ。
この説明は以下のブログが詳しい。
重要.icon React: アプリ内のエラーを全部まとめて処理する(react-error-boundary)
この記事まじでわかりやすいので要チェックな。
ただ、記事内でuseErrorHandlerって出てくるけど、バージョンによってはuseErrorBoundaryに変わってる可能性あるから注意。
bvaughn/react-error-boundary: Simple reusable React error boundary component
かんったんに要約すると...
react-error-boundaryを使うと、、、以下のエラー全てまとめて処理できます。
コンポーネントレンダリング系のエラー
イベントハンドラーのエラー
非同期処理のエラー
コラム:開発時に「ErrorBoundary」コンポーネントを使う場合、直感と異なる挙動するので注意!!
具体的には、React開発時に動作するErrorBounaryはエラーを受けたら、そいつを再スローしよる...。
(react開発時ってのは、例えばcreate-react-appとかで作成したプロジェクトで、yarn startとかで起動するやつ)
なので、開発時に「あれ!!ErrorBoundaryでキャッチして処理してるのに、キャッチしてなさそう??」と疑問になった場合は、この仕様を思い出すといい。
参考:
https://github.com/facebook/react/issues/10474#issuecomment-1245078343
React Suspense での非同期処理のエラー処理パターン
ところで、ErrorBoundary は開発モードの React では catch したエラーを rethrow します。
Suggestion: don't re-throw errors caught by boundaries (in development) · Issue #19613 · facebook/react
Reactエラー関連Tips
hr.icon
開発環境として動作させてる時と、ビルドして本番で動作させる時の例外発生挙動が異なる...
「React エラーハンドリング#644a5c19b3641f0000c6b84f」とよく似た状況。
開発環境で適当にエラーを投げたら、2回エラーが送出される。。
コンソールには2回、そのエラー情報が流れる。
その仕組みはよくわからんけど、開発環境で送出してるからそうなるっぽい。
本番だと1回しかエラー出ない。
開発環境の場合以下のようにエラーが2回出力される
https://scrapbox.io/files/644ce80246d601001be0cbeb.png
本番だと1回
https://scrapbox.io/files/644ce82c7c6482001b4e63a9.png
try...catchで処置されなかったエラーは最終的に、デフォルト?で設定されてるグローバルのイベントハンドラーに捕まり、「Uncaught Error」として出力されるかも
window.onerrorとかwindow.addEventListener("error")とかで、最終的にエラーキャッチしてるのになんで?って思ってたが。
まあ、そういったイベントハンドラーを登録しても、その上のハンドラーにエラーがバブルアップされてしまう。
なので、こっちで自前でイベントリスナー登録しても、それで例外おわりってわけじゃない。
もし、例外終わりにしたいなら、登録したwindow.onerrorとかwindow.addEventListener("error")の中でevent.preventDefault()を使うしかない。
なんかあまり挙動わかってないので、使うかどうかは自己判断で...。
いずれにせよ、Uncaught Errorがコンソールに出力されちゃう理由はなんとなくわかった。
思考
hr.icon
使う技術というか構成というか
「ErrorBoundary」の仕組みは結構いいんだけど、非同期処理やイベントハンドラーのエラーキャッチがどうも冗長だなと。
いちいち、try...catch構文使って、useErrorBoundary使わないといけないのは結構きつい。
「エラー出たとしても結局回復できねえよ」っていうエラーに関しては、try..catch構文を書くの冗長じゃね?
code: sample.ts
...
const { showBoudndary } = useErrorBoundary();
function handleClick() {
try {
// なんかエラー出るかもな処理
} catch (error) {
showBoundary(error);
}
}
これわざわざtry...catchで囲むの冗長だろ。
あと、人間誰しも忘れることもある。
もしtry...catch構文忘れた場合でも、どこかでキャッチしてくれる防護ネット的なの必要なんじゃない?
あとはそうだな、最上位ルートのErrorBoundaryだけ用意して、そいつに全コンポーネントのハンドリングさせようと思ってたけど、そうか、必要であれば子供のコンポーネントでErrorBoundary使って、勝手にキャッチするのもありなのか。
ふむ。フロントエンドのそういう塩梅がまだわからん。
ふむ、、、
ErrorBoundaryを中心にして例外処理を実装していくことは間違いないんだが。少しモヤモヤしているな。
非同期処理、イベントハンドラ内で発生した例外の最後の防護ネットがないような気がする。
例えば、try...catch構文を書かないまま実行してる非同期処理とかがあった際に、そいつのエラーが発生したら誰が最後キャッチするんだ?という。
誰もキャッチできないよなぁ。っていう。
なんかカスタムフックで上手いこと、そのコンポーネント内の溢れ出た例外をキャッチするとかできねぇか???
やるとしたら、グローバルで例外をキャッチするとかになりそう。
参考:DEV でエラーを再スローするための偽のイベント トリックにより、予期しないグローバル エラー ハンドラが起動され、テストが困難になる · 問題 #10474 · facebook/react
code: sample.js
window.addEventListener("error", function (event) {
let {error} = event;
...
// エラーログをサーバーに送信
});
以上から、一旦はErrorBounaryとグローバルイベントerrorをキャッチする2つの方法を使って、Reactにおけるエラーハンドリングができそうかな。
以下のルールをまずは守る感じだろうか。
1. レイヤー系のエラーは全部最上位のErrorBounaryで受け取れるようにしておく
2. 非同期・イベントハンドラー内で起きる例外も、その中で明示的にtry...catch使ってエラーをErrorBoundaryに送るようにする。
3. それでもキャッチできなかった例外は、グローバルに登録してるerrorキャッチのリスナーに任せる。
「3.」で行うハンドリングに関しては、よっぽどの要件がない限りは、ログをサーバーに送るとかになるかなぁ。
ちなこれ、window.onerrorってのを使うのもありっぽい。
エラーハンドリング, "try..catch"
あとはカスタムエラーとかも必要かなぁ
以下に記載してるから、これ参考にしようか。
カスタムエラー, Error の拡張
そもそもキャッチする例外とそうでない例外とか
レイヤー系の例外は画面が真っ白になるから対応するのは必須として。
非同期系って対応しなくてもいいのと、そうでないのありそうな気もする。
例えば「画面全体の中の1部機能に関する非同期で例外起きたけど、それ以外の機能は普通に使えてる」ていう状況。
この場合に、ユーザーの操作を止めて「なんかわからんけどエラー起きました」って全画面表示を出すのは最悪のUXな気がする。
どういうタイミングでユーザーの行動を止めるべきか?って話になるんだと思う。
で、安全側に倒すなら、開発時には想定しなかったエラーが起きたなら、それが非同期中であったとしてもユーザーの行動を一旦止めるってことになるのかな?
間違ってる状況なのにも関わらず、そのままユーザーが次の行動を取ってしまって取り返しのつかないことなるとか起きる?
非同期処理で起きたエラーに対してどう対処するのが正解なんだろうか。。。
ユーザーが期待してる結果が返らないなら、何か行動を起こすべき。
期待してる結果に影響がないなら、まあ最悪何も行動を起こさなくていい。
ユーザーに悪影響を与える可能性のあるエラーに関しては、そのエラーが起きる箇所に近いところで回復処理を入れることが重要かな。
まずそれが大前提、その悪影響を受けてしまう系のエラーのキャッチ、ハンドリングを逃さない。
グローバルのキャッチで一元的にハンドリングさせるのは危ない。
だから逆にグローバルでキャッチすることになったエラーハンドリングに関しては、ユーザーの行動を止めるとかはしないことにする。
そういう系のエラーは、発生する箇所で慎重に対処されるべきという前提を作ったから。
なので、グローバルでやることは、まあログの送信くらいかな。
Reactベストプラクティスの宝庫!「bulletproof-react」が勉強になりすぎる件
エラーが起きたら、トップページに戻らせるという方針もありか。
情報Input
hr.icon
React のエラー処理とログのベスト プラクティス | ビットアンドピース
「react-error-boundary使いな」しか言ってないから、そこから得れるものはないonigiri.w2.icon
ロギングについては見ておこう。
Sentry は React 用のカスタム エラー境界コンポーネントを提供し、コンポーネント ツリー内の JavaScript エラーを自動的に Sentry に送信します。React の基本的なエラー境界コンポーネントと同様に使用できます。
ほおおおお、わざわざログ収集機構作らずに、そのサービスにログ集めてくれるってこと???
todo.iconこれは気になる。メモ。
エラー監視ツール Sentryの導入方法(TeamPlan)(Golang)
console.log()ログ レベルは、ログをより詳細に制御できるレベルベースのロギングおよびフィルタリング機能を備えた標準に取って代わります。Sentry や redux-logger よりも機能は少ないですが、ほとんどの人が必要とする基本的な機能を備えています。
ええ、console.logってconsole.errorとかできるんか知らんかったonigiri.w2.icon
覚えておこう。
loglevel
loglevel-plugin-remote を使用して、ストレージ、分析、およびアラートのためにログをリモート ログ サーバーに送信できます。プレーン テキストを送信するのではなく、ログを簡単に並べ替えて整理できるように、JSON オブジェクトとして送信する必要があります。
なるほど、log.infoとか使う感じで、それで貯めたらログをどこかのサーバーに送るとかできるんか。
ほおおおおお、いいねonigiri.w2.icon
React での効果的なエラー処理ガイド - Upmostly
1. 常にエラーを処理する
アプリケーションで発生したエラーを処理することは重要です。エラーの原因がコードの問題か、ネットワークの問題や API エラーなどの外的要因かは関係ありません。
エラーの処理に失敗すると、予期しない動作やユーザーの不満につながる可能性があります。
無視していいエラーなんてないってことか?onigiri.w2.icon
いや、ユーザーが大きな不利益を被らないなら、あえて無視っていう選択もあるよな。
サーバー側に「なんかエラー起きてんで」て報告するだけで、ユーザーにはエラー発生を伝えない的な。
例えば、適当に情報収集するためのAPIがユーザー操作の裏で動いてたとして、そのAPIが壊れたエラーが出たとしても、別にユーザーの動作を止める必要はないだろう。
いやまあいずれにせよ、あえて無視するか、意図しないで無視するかは影響が違うかもや。
2. try-catch ブロックを控えめに使用する
try-catch ブロックは単純なケースの処理に役立ちますが、アプリケーションが大きくなるにつれてすぐに扱いにくくなる可能性があります。
componentDidCatch一般に、ライフサイクル メソッドを使用してエラーを一元的に処理することをお勧めします。
非同期処理どうすんねん、どうやって一元的に管理するんなonigiri.w2.icon
全てのエラーを一元的に管理しようとすると、柔軟性なくなるんじゃないか。
一元管理の定義もちゃんと把握してないけど、ふむ。
3. 明確なエラー メッセージを表示する
エラーが発生した場合は、明確で簡潔なエラー メッセージをユーザーに表示することが重要です。これにより、問題の原因とその修正方法を理解することができます。
ユーザーの操作で復帰できるエラーと復帰できないエラーに分かれるよなonigiri.w2.icon
復帰できるエラーなら、正確なエラーメッセージが必要だと思う。
でも復帰できないエラーはもう仕方ない。諦めてごめんなさいしよう。そして報告してもらう。
4. デバッグ用にエラーをログに記録する
ユーザーにエラー メッセージを表示するだけでなく、デバッグのためにエラーをログに記録することもお勧めします。これにより、アプリケーションの問題をより迅速に特定して修正できます。
これはサーバーに記録するってこと?onigiri.w2.icon
ユーザーのブラウザに記録したって意味ないもんな。
待てよ!わかった
「ユーザーレポート」て感じのアクション出して、それが実行されたら、デバッグメッセージもろとも送信させる方法もありなんじゃね?
6. エラー処理ロジックをテストする
エラー処理ロジックをテストして、期待どおりに動作していることを確認することを忘れないでください。
Jest や Enzyme などのツールを使用してコンポーネントの単体テストを記述し、Postman や Curl などのツールを使用してネットワーク エラーをシミュレートし、さまざまな条件下でエラー処理ロジックをテストできます。
これはそうですよね、念入りにテストしないと意味ないすよねonigiri.w2.icon
エラーハンドリングがバグってたら全て終わる。
テストサボりがちだけど、肝に銘じておきます...
7. エラー監視ツールを使用する
アプリケーションで発生する可能性のあるすべてのエラーを予測してテストできるとは限りません。
本番環境で発生したエラーを特定して修正するには、Sentry や Rollbar などのエラー監視ツールの使用を検討してください。
これらのツールを使用すると、エラーをリアルタイムで追跡し、スタック トレースやコンテキスト データなど、エラーに関する詳細情報を提供できます。
これいいけど、もはや個人の域を超えてる気がするんよねonigiri.w2.icon
最初のうちからこれ使ってたら金が足りないんじゃないだろうか。
無料期間とか用意してくれてるかね
React での例外処理: ベスト プラクティス
コンポーネント内のエラーを検出し、エラーの詳細を含むフォールバック コンポーネントを表示し、状態をリセットして同じ問題の再発を防ぐ効果的な方法であるため、アプリケーションでエラー境界の作成と使用を正規化します。
これ1つだけのエラー境界で対応するの難しくね?直感的にそう思ったonigiri.w2.icon
例外を処理するためにブラウザー固有の構文を使用しないでください。そうしないと、他のブラウザーを検出するためにその中に新しい例外が必要になります。理想的には、例外内の例外は望ましくない慣行であるため、すべてのブラウザー サポートに対して例外を適切に定義することがベスト プラクティスです。
はいonigiri.w2.icon